Skip to content

[GSoC] [WIP] PipeWire support - #16590

Merged
daschuer merged 13 commits into
mixxxdj:mainfrom
pri-yan-shu:pipewire
Jul 8, 2026
Merged

[GSoC] [WIP] PipeWire support#16590
daschuer merged 13 commits into
mixxxdj:mainfrom
pri-yan-shu:pipewire

Conversation

@pri-yan-shu

@pri-yan-shu pri-yan-shu commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

This implements support for PipeWire backend.

This is tracking this proposal, any project scope related discussion can happen there.

You can test this PR by selecting the PipeWire Sound API in the Sound Hardware preference page.

From the proposal, currently what is working:

  • Implement support for PipeWire backend
  • Modify PipeWire graph from Mixxx UI
  • Hotplug (adding/removing device, adding/removing ports on a device, connecting/disconnecting links to and from Mixxx node)

@pri-yan-shu pri-yan-shu changed the title [GSoC] [draft] PipeWire support [GSoC] [WIP] PipeWire support Jun 11, 2026
@github-actions github-actions Bot added the developer experience Issues, bugs and PRs related to the development process, development environment & developer docs label Jun 11, 2026

@daschuer daschuer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some first comments.

Comment thread CMakeLists.txt Outdated
target_link_libraries(mixxx-lib PRIVATE PortAudioRingBuffer)

# PipeWire
default_option(PIPEWIRE "Enable the PipeWire backend" "LINUX")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
default_option(PIPEWIRE "Enable the PipeWire backend" "LINUX")
default_option(PIPEWIRE "Enable the PipeWire backend" "UNIX AND NOT APPLE")

Comment thread tools/debian_buildenv.sh
libshout-idjc-dev \
libsndfile1-dev \
libsoundtouch-dev \
libsqlite3-dev \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
libspa-0.2-dev \
libsqlite3-dev \

Comment thread tools/debian_buildenv.sh
libmsgsl-dev \
libopus-dev \
libopusfile-dev \
libpipewire-0.3-dev \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please edit the fedora and flatpak recipes as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does flatpak need any change? I only see: REQUIRED_PACKAGES=("org.kde.Platform//6.10" "org.kde.Sdk//6.10")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread CMakeLists.txt Outdated
default_option(PIPEWIRE "Enable the PipeWire backend" "LINUX")
if(PIPEWIRE)
find_package(PkgConfig REQUIRED)
if(PKG_CONFIG_FOUND)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move this to the cmake/modules folder. We need one file for libpipewire the libspa part can be probably be integrated?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If detecting through pkg-config like here, libspa is automatically included, it is required package by libpipewire.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It failed in my case Ubuntu Jammy (dropped for main, I know) I need to double check for others.
In any case we use these find modules.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, perhaps its not a required dependency in pipewire's PC file on some platforms (on my distro pkg-config gives flags for libspa as well along with libpipewire), we can add libspa separately. Do I keep the detection like how it happens currently, using pkg-config, or add a manual fallback as well?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have never understood why all of these find fils have the manual fall back.
In this case pkgconfig is part of the upstream project so we can skip the fallback IMHO

Comment thread src/soundio/pipewireenumerator.cpp Outdated
PW_KEY_MEDIA_NAME,
};

SPA_FOR_EACH_ELEMENT_VAR(name_keys, key) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Luckily C++ has a native range based loop, better known by the reader.

Suggested change
SPA_FOR_EACH_ELEMENT_VAR(name_keys, key) {
for (const char* key : name_keys) {

Comment thread src/soundio/sounddevicepipewire.cpp Outdated
m_hostAPI = "PipeWire";
m_deviceId.name = name.data();

qWarning() << "PipeWire device: " << name;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
qWarning() << "PipeWire device: " << name;
qWarning() << "PipeWire device: " << name.data();

@pri-yan-shu

Copy link
Copy Markdown
Contributor Author

I have some commits here which were squashed in the soundmanager-refactor, is it okay if I force push here once? All current reviews have been addressed.

@daschuer

Copy link
Copy Markdown
Member

Yes go ahead and rebase it on main.

@daschuer

Copy link
Copy Markdown
Member

I experience a segmentation fault after restarting pipewire-jack and "querry devices"

Thread 34 "mixxx_pw_loop" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fff86ffd640 (LWP 9750)]
0x00005555568a093e in __gnu_cxx::__normal_iterator<SoundDevicePipewire::DevicePort*, std::vector<SoundDevicePipewire::DevicePort, std::allocator<SoundDevicePipewire::DevicePort> > >::operator+ (__n=<optimized out>, this=<optimized out>) at /usr/include/c++/11/bits/stl_iterator.h:1091
1091          { return __normal_iterator(_M_current + __n); }
bt
#0  0x00005555568a093e in __gnu_cxx::__normal_iterator<SoundDevicePipewire::DevicePort*, std::vector<SoundDevicePipewire::DevicePort, std::allocator<SoundDevicePipewire::DevicePort> > >::operator+(long) const (__n=<optimized out>, this=<optimized out>) at /usr/include/c++/11/bits/stl_iterator.h:1091
#1  SoundDevicePipewire::unregisterDevicePort(unsigned int, spa_direction) (this=0x0, id=<optimized out>, direction=SPA_DIRECTION_OUTPUT) at /home/daniel/workspace/mixxx/src/soundio/sounddevicepipewire.cpp:148
#2  0x00007fffa8750438 in registry_demarshal_global_remove (object=0x555557a866a0, msg=<optimized out>) at ../src/modules/module-protocol-native/protocol-native.c:1809
#3  0x00007fffa8741952 in process_remote (impl=impl@entry=0x555557a7c360) at ../src/modules/module-protocol-native.c:851
#4  0x00007fffa8741f48 in on_remote_data (data=0x555557a7c360, fd=37, mask=1) at ../src/modules/module-protocol-native.c:885
#5  0x00007fffd3eeee33 in loop_iterate (object=0x555557a60238, timeout=<optimized out>) at ../spa/plugins/support/loop.c:368
#6  0x00007ffff690a27a in do_loop (user_data=0x555557a6f110) at ../src/pipewire/thread-loop.c:262
#7  0x00007ffff1e94ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#8  0x00007ffff1f268d0 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

Not sure how to fix it.

@pri-yan-shu

Copy link
Copy Markdown
Contributor Author

Commit 8fc429a solved crash like this on my local machine, but still there are test failures on CI, whereas no test failures on my local machine.

@daschuer

Copy link
Copy Markdown
Member

We have an issue with the Flatpack build:

145 - ControllerScriptEngineLegacyTest.commonScriptHasNoErrors (SEGFAULT)

And more.

Comment thread src/soundio/pipewireenumerator.cpp Outdated
s.remove('[');
s.remove(']');

QStringList parts = s.split(',', Qt::SkipEmptyParts);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
QStringList parts = s.split(',', Qt::SkipEmptyParts);
const QStringList parts = s.split(',', Qt::SkipEmptyParts);

@daschuer

Copy link
Copy Markdown
Member

I have still a segfault when Querry Devices after messing with QJackCrtl.

Thread 34 "mixxx_pw_loop" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fff8affd640 (LWP 20639)]
0x00005555568a095e in __gnu_cxx::__normal_iterator<SoundDevicePipewire::DevicePort*, std::vector<SoundDevicePipewire::DevicePort, std::allocator<SoundDevicePipewire::DevicePort> > >::operator+ (__n=<optimized out>, this=<optimized out>) at /usr/include/c++/11/bits/stl_iterator.h:1091
1091          { return __normal_iterator(_M_current + __n); }
bt
#0  0x00005555568a095e in __gnu_cxx::__normal_iterator<SoundDevicePipewire::DevicePort*, std::vector<SoundDevicePipewire::DevicePort, std::allocator<SoundDevicePipewire::DevicePort> > >::operator+(long) const (__n=<optimized out>, this=<optimized out>) at /usr/include/c++/11/bits/stl_iterator.h:1091
#1  SoundDevicePipewire::unregisterDevicePort(unsigned int, spa_direction) (this=0x0, id=<optimized out>, direction=SPA_DIRECTION_OUTPUT) at /home/daniel/workspace/mixxx/src/soundio/sounddevicepipewire.cpp:150
#2  0x00007fffa0750438 in registry_demarshal_global_remove (object=0x555557a87b90, msg=<optimized out>) at ../src/modules/module-protocol-native/protocol-native.c:1809
#3  0x00007fffa0741952 in process_remote (impl=impl@entry=0x555557a85860) at ../src/modules/module-protocol-native.c:851
#4  0x00007fffa0741f48 in on_remote_data (data=0x555557a85860, fd=37, mask=1) at ../src/modules/module-protocol-native.c:885
#5  0x00007fffd3eeee33 in loop_iterate (object=0x555557a56108, timeout=<optimized out>) at ../spa/plugins/support/loop.c:368
#6  0x00007ffff690a27a in do_loop (user_data=0x555557a1d190) at ../src/pipewire/thread-loop.c:262
#7  0x00007ffff1e94ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#8  0x00007ffff1f268d0 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
``

Comment thread src/soundio/sounddevicepipewire.h Outdated
Comment on lines +53 to +55
static void callback(void* data, spa_io_position* pos);
static void callbackDrift(void* data, spa_io_position* pos);
static void callbackClkRef(void* data, spa_io_position* pos);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We move private static out of the class body into the anonymous namespace of the cpp file, to keep the class definition small.

Please prefix all pointer variables with p

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One issue is that since the member function these are calling are private, these functions need to be in class definition. Is there a way out, or I make the member functions public?

Comment thread cmake/modules/FindPipeWire.cmake
Comment thread src/soundio/networkenumerator.cpp Outdated
Comment on lines +6 to +7
NetworkEnumerator::NetworkEnumerator(UserSettingsPointer config,
SoundManager* sm)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
NetworkEnumerator::NetworkEnumerator(UserSettingsPointer config,
SoundManager* sm)
NetworkEnumerator::NetworkEnumerator(UserSettingsPointer pConfig,
SoundManager* pSm)

Comment thread src/soundio/pipewireenumerator.cpp Outdated
Comment on lines +35 to +36
PipewireEnumerator::PipewireEnumerator(
[[maybe_unused]] UserSettingsPointer pConfig, SoundManager* pManager)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
PipewireEnumerator::PipewireEnumerator(
[[maybe_unused]] UserSettingsPointer pConfig, SoundManager* pManager)
PipewireEnumerator::PipewireEnumerator(
UserSettingsPointer, SoundManager* pManager)

@daschuer

Copy link
Copy Markdown
Member

After merging #16544 we have conflicts. Can you rebase it again?

@daschuer

Copy link
Copy Markdown
Member

The crash happens here:
https://github.com/PipeWire/pipewire/blob/6c4d3a51583f823b789b0de2df1e36d6c2f8dff8/src/modules/module-protocol-native/protocol-native.c#L1809
This calls PipewireEnumerator::registryEventGlobalRemove()
and the crashes in unregisterDevicePort()

@pri-yan-shu

Copy link
Copy Markdown
Contributor Author

This is better in case of using queryDevices after restarting PipeWire server, although it will take work to get it working after a server restart. Also, currently there is a quirk while using Pipewire API, when restarting devices, on even times ports on Mixxx's node appear in jumbled order, and then testing hotplug crashes Mixxx.

@daschuer daschuer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more comments.

Comment thread src/soundio/pipewireenumerator.cpp Outdated
}

std::vector<SoundDevicePointer> PipewireEnumerator::queryDevices() const {
std::vector<SoundDevicePointer> devices{};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::vector<SoundDevicePointer> devices{};
std::vector<SoundDevicePointer> devices;

{} is redundant

Comment thread src/soundio/pipewireenumerator.cpp Outdated
auto device = QSharedPointer<SoundDevicePipewire>::create(
m_pConfig, m_pManager, this, id, name);
emit deviceAdded(device);
m_soundDevices.insert_or_assign(id, std::move(device));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it correct that we overwrite the device, if we have already one? Please add a comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, each object is assigned a unique ID, so it will be removed beforehand if same ID is reused.

Comment thread src/soundio/pipewireenumerator.cpp Outdated
}

void PipewireEnumerator::registryEventGlobal(uint32_t id,
[[maybe_unused]] uint32_t permissions,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[[maybe_unused]] uint32_t permissions,
uint32_t,

Comment thread src/soundio/pipewireenumerator.cpp Outdated
device->close();
emit pEnumerator->deviceRemoved(device);
} else if (auto* port = std::get_if<Port>(&object)) {
pEnumerator->m_soundDevices[port->nodeId]->unregisterDevicePort(port->id, port->direction);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume that m_soundDevices[port->nodeId] is a null or dangling pointer.

Comment thread src/preferences/dialog/dlgprefsound.cpp Outdated
}

void DlgPrefSound::disconnectDevice(const AudioPath* path) {
for (auto* soundItem : m_selectedInputChannelIndices.keys()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (auto* soundItem : m_selectedInputChannelIndices.keys()) {
for (auto* pSoundItem : std::as_const(m_selectedInputChannelIndices.keys())) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean this:

-    for (auto* pSoundItem : std::as_const(m_selectedInputChannelIndices).keys()) {
+    for (const auto* pSoundItem : std::as_const(m_selectedInputChannelIndices).keys()) {

@daschuer daschuer Jun 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that's probably a bit tricky. We have a const pointer pointing to non const.
So we want this:

Suggested change
for (auto* soundItem : m_selectedInputChannelIndices.keys()) {
for (const auto& pSoundItem : std::as_const(m_selectedInputChannelIndices.keys())) {

This is a const reference to the non const pointer. "Von hinten durch die Brust ins Auge" a German phrase.
But that matches the best practice when using range based loops and the compiler does the optimum.

Comment thread src/soundio/sounddevicepipewire.cpp Outdated
}

void SoundDevicePipewire::writeOutput(float* output, int channel, int framesPerBuffer, int offset) {
for (auto i = m_audioOutputs.constBegin(), e = m_audioOutputs.constEnd(); i != e; ++i) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can become a range based loop

const CSAMPLE* pOutputBuffer = &in.getBuffer()[offset];

if (iChannelCount == 1) {
for (int i = 0; i < framesPerBuffer; i++) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the sampleutil function shall be used.


if (iChannelCount == 1) {
if (input) {
for (int i = 0; i < framesPerBuffer; i++) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, use sampleUtil functions for this and introduce missing once if not there.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean

static void interleaveBuffer(CSAMPLE* pDest, const CSAMPLE* pSrc1,
            const CSAMPLE* pSrc2, SINT numSamples);

and allocate zeroed buffer if missing? What do I use for allocation, alloca?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need a new similar function because the buffer is composed in two steps.

It the other channel is never update the buffer contains the old data. This is bad on one hand, but maybe less notable than silence.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw one approach where if input buffers are not available we allocate one with alloca (which allocates on the stack only but is not standardized), zero it and then we can use it with interleaveBuffer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic is already good. We cant allocate buffers in the audio thread, because this is a locking operation. I jst meant to refactor out the copy loops. Than we have a nice name and we chan check for vectorization together with the other sampleutil functions whenever we refactor anything related.

output[i] = pOutputBuffer[i * 2];
}
} else {
for (int i = 0; i < framesPerBuffer; i++) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here as well, you may introduce a new one if it is missing.

Comment thread src/soundio/sounddevicepipewire.h Outdated
void writeProcess(SINT) override {
}
QString getError() const override {
return "";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return "";
return {};

@pri-yan-shu

Copy link
Copy Markdown
Contributor Author

Hotplug is working fine now, I haven't seen any crash with limited testing. I'm not too sure of the approach for concurrently accessing variables from callback thread, any suggestions are welcome.

@daschuer

Copy link
Copy Markdown
Member

We typical use ControlObjects for this purpose. But it depends. Can you point me to the case?

@pri-yan-shu

Copy link
Copy Markdown
Contributor Author

For instance in SoundDevicePortAudio:

std::atomic<PaStream*> m_pStream;
std::atomic<int> m_callbackResult;

in PipewireEnumerator I'm using:

    using SoundDeviceMap = std::unordered_map<uint32_t, QSharedPointer<SoundDevicePipewire>>;
    std::atomic<std::shared_ptr<SoundDeviceMap>> m_soundDevices;

    using DeviceMap = std::unordered_map<uint32_t, Device>;
    std::atomic<std::shared_ptr<DeviceMap>> m_openedDevices;

to update common state from pipewire thread while callback RT thread is reading same vars.

@daschuer

Copy link
Copy Markdown
Member

Do you have an idea why we have CI errors?

Flatpak:

2026-06-22T05:17:45.5219406Z The following tests FAILED:
2026-06-22T05:17:45.5219761Z 	146 - ControllerScriptEngineLegacyTest.commonScriptHasNoErrors (SEGFAULT)
2026-06-22T05:17:45.5220217Z 	147 - ControllerScriptEngineLegacyTest.setValue (SEGFAULT)
2026-06-22T05:17:45.5220832Z 	148 - ControllerScriptEngineLegacyTest.getValue_InvalidKey (SEGFAULT)
2026-06-22T05:17:45.5221351Z 	149 - ControllerScriptEngineLegacyTest.setValue_InvalidControl (SEGFAULT)
2026-06-22T05:17:45.5221968Z 	150 - ControllerScriptEngineLegacyTest.getValue_InvalidControl (SEGFAULT)
2026-06-22T05:17:45.5222442Z 	151 - ControllerScriptEngineLegacyTest.setValue_IgnoresNaN (SEGFAULT)
2026-06-22T05:17:45.5222879Z 	152 - ControllerScriptEngineLegacyTest.getSetValue (SEGFAULT)
2026-06-22T05:17:45.5223288Z 	153 - ControllerScriptEngineLegacyTest.setParameter (SEGFAULT)
2026-06-22T05:17:45.5223743Z 	154 - ControllerScriptEngineLegacyTest.setParameter_OutOfRange (SEGFAULT)
2026-06-22T05:17:45.5224215Z 	155 - ControllerScriptEngineLegacyTest.setParameter_NaN (SEGFAULT)

clazy

[1](https://github.com/mixxxdj/mixxx/actions/runs/27930676538/job/82641808948?pr=16590#step:10:1092)
Error: /home/runner/work/mixxx/mixxx/src/soundio/soundmanager.cpp:678:5: error: c++11 range-loop might detach Qt container (QList) [-Wclazy-range-loop-detach]
  678 |     for (auto device : m_devices) {
[ 57%] Building CXX object CMakeFiles/mixxx-lib.dir/src/sources/audiosourcestereoproxy.cpp.o
      |     ^                  ~~~~~~~~~
      |                        std::as_const(m_devices)
Error: /home/runner/work/mixxx/mixxx/src/soundio/soundmanager.cpp:678:10: error: Missing reference in range-for with non trivial type (QSharedPointer<SoundDevice>) [-Wclazy-range-loop-reference]
  678 |     for (auto device : m_devices) {
      |          ^
      |          const  &
2 errors generated.

Ubuntu

The following tests FAILED:
	146 - ControllerScriptEngineLegacyTest.commonScriptHasNoErrors (SEGFAULT)
Errors while running CTest
	147 - ControllerScriptEngineLegacyTest.setValue (SEGFAULT)
	148 - ControllerScriptEngineLegacyTest.getValue_InvalidKey (SEGFAULT)
	149 - ControllerScriptEngineLegacyTest.setValue_InvalidControl (SEGFAULT)
	150 - ControllerScriptEngineLegacyTest.getValue_InvalidControl (SEGFAULT)

@pri-yan-shu

Copy link
Copy Markdown
Contributor Author

With the latest commits, using podman docker.io/library/ubuntu:24.04, I am passing all tests.

@pri-yan-shu

Copy link
Copy Markdown
Contributor Author

The builds don't pick up the changes from last --fixup commit, is this normal?

@daschuer daschuer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added some comments regarding the atomic<shared_ptr>
Form a practically aspect on a multi core CPU the solution is probably good enough.
However let's be strict and don't introduce locks.

I don't have a receipt yet. Only two ideas.
We need a pair of

  • port.pPortData / pSoundDevice
    For my understanding that can change at any time because of external patch bays.
    Right? We cant stop the callback for reconfiguration like we do with portaudio.

SoundDevicePipewire pSoundDevice feels foreign, like an artefact form the past.
Do we actually have the Mixxx ports, somehow the inverse of a device?
This can be a static list, somehow the input and output items.
Will this also solve the solution to see Mixxx ports in the patechbay, even though they are not selected in the Mixxx preferences?

Can we have more port.pPortData than buffers in Mixxx? That's the part I have not yet understood.

So one Idea is to make it all static and rely on the routing in pipewire.

  • Mixxx preferences controls the graph like an external patchbay.
  • The callback just fills and read the buffers Mixxx has. (no dynamic allocation)

The other idea is to use a input cue or a double swap. This must be implemented in a way that only the gui thread deletes old objects.
We have implemented such a cue in the effect unit, for adding and removing effect controls.

This may also give you some ideas:
https://github.com/mixxxdj/mixxx/blob/main/src/util/borrowable_ptr.h

I

Comment on lines +51 to +52
void addDevice(const SoundDevicePointer pDevice);
void removeDevice(const SoundDevicePointer pDevice);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using const in that case is misleading and an C++ antipatter. No const value types in function declarations. You may use const in the function definition, but that's also misleading. In the case the value the pointer is pointing to is not const.

Suggested change
void addDevice(const SoundDevicePointer pDevice);
void removeDevice(const SoundDevicePointer pDevice);
void addDevice(SoundDevicePointer pDevice);
void removeDevice(SoundDevicePointer pDevice);

Comment thread src/soundio/pipewireenumerator.h Outdated

using SoundDeviceMap = std::unordered_map<uint32_t, QSharedPointer<SoundDevicePipewire>>;
std::atomic<std::shared_ptr<SoundDeviceMap>> m_soundDevices;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have her:

  • atomic
    * shared controls structure
    * pointer
    |
    |
    • Map
      • key/value pair
        • Shared pointer

A plain shared pointer can be accessed across different threads, because its control structure is thread safe.
The only thing is which is not thread safe is to exchange the control structure, aka setting a new pointer to the same share pointer object. It is also not thread save to access the object the pointer is pointing to, unless is object is guaranteed to be const.

std::atomic<std::shared_ptr<>> does allow to exchange the pointer at runtime from different threads. This is however not an atomic operation in the sense of real time audio,

We may still have a race condition, because concurrent read and write access to std::unordered_map<> is not allowed form different threads.

Comment thread src/soundio/pipewireenumerator.cpp Outdated
m_ppwRegistry(nullptr),
m_ppwMetadata(nullptr),
m_ppwFilter(nullptr),
m_soundDevices(std::make_shared<SoundDeviceMap>()),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here a new pointer is added during the constructor. This is always OK, because no other thread has access yet.

Comment thread src/soundio/pipewireenumerator.cpp Outdated
auto device = QSharedPointer<SoundDevicePipewire>::create(
m_pConfig, m_pSoundManager, this, id, name);
emit deviceAdded(device);
auto pSoundDevices = std::make_shared<SoundDeviceMap>(*m_soundDevices.load());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we have a deep copy of the SoundDeviceMap, sharing the SoundDevicePipewire pointers.

Comment thread src/soundio/pipewireenumerator.cpp Outdated
// pipewire assigns each object with a unique ID
// any previous element is either invalid or already removed
pSoundDevices->insert_or_assign(id, std::move(device));
m_soundDevices.store(pSoundDevices);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we write back the changed SoundDeviceMap, potentially the unsafe action, here secured by the std::atomic, which is not lock free in this case.

Comment thread src/soundio/pipewireenumerator.cpp Outdated
Object& object = pair.mapped();

if (std::get_if<Node>(&object)) {
auto pSoundDevices = std::make_shared<SoundDeviceMap>(*m_soundDevices.load());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deep copy of SoundDeviceMap

Comment thread src/soundio/pipewireenumerator.cpp Outdated

qWarning() << "removing device:" << pDevice->getDisplayName();
pSoundDevices->erase(id);
m_soundDevices.store(pSoundDevices);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write back.

Comment thread src/soundio/pipewireenumerator.cpp Outdated
m_pSoundManager->processUnderflowHappened(framesPerBuffer);

auto pOpenedDevices = m_openedDevices.load();
auto pSoundDevices = m_soundDevices.load();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using auto here, is problematic, because you can't guess the type. In addition it is complicated code.

This is the probably locking call in the audio engine thread. atomic<shared_ptr> has the atomic API, but is not lock free.

You only increase the reference counter of the SoundDeviceMap so it does not fall out of scope when it is replaces in the GUI thread. However when this is the last instance of the share pointer because the GUI thread has replaced it. the engine thread will delete it at the end if this function. This must not happen, because delete() is also a locking function.

Comment thread src/soundio/pipewireenumerator.cpp Outdated

for (auto& [id, device] : *pOpenedDevices) {
auto pSoundDevice = pSoundDevices->at(id);
auto& ports = device.inputs;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is also too much auto to understand the code on a review.


if (iChannelCount == 1) {
if (input) {
for (int i = 0; i < framesPerBuffer; i++) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic is already good. We cant allocate buffers in the audio thread, because this is a locking operation. I jst meant to refactor out the copy loops. Than we have a nice name and we chan check for vectorization together with the other sampleutil functions whenever we refactor anything related.

@daschuer

daschuer commented Jul 5, 2026

Copy link
Copy Markdown
Member

The code looks good. Please rebase and squash the commits.

Is it easy to remove the Mixxx presentation form pipewire if ALSA is used? If yes it would be nice to have it fixed here. Else we can fix it later.

@pri-yan-shu

pri-yan-shu commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

The way currently preference page works, devices appear instantaneously when PipeWire API is selected, which requires preemptive initialization. Either PipeWire initialization is delayed till queryDevices(), in that case the UX becomes:

  • select PipeWire api
  • press queryDevices
  • now devices appear in comboBox

Or as a compromise, PipeWire is deinitialized on preference page close, but that just delays the above UX till next Sound API switch.

Or we can initialize/deinitialize directly on Sound API comboBox change. This is the cleanest approach.

This issue is other way also, portaudio initializes if PipeWire used.

@pri-yan-shu
pri-yan-shu force-pushed the pipewire branch 2 times, most recently from 9321901 to 0323184 Compare July 6, 2026 19:51
since PipeWire SoundDevices will be using this property
portAudioIndex is inappropriate name
Hotplug is for node/port creation/destruction.
Since how pipewire objects show up on the registry, when a SoundDevice
is created and the preference page is signalled, it has no ports,
so it is not added to DlgPrefSound or any DlgPrefSoundItem
(although currently the code is signalling on node creation anyway).
On subsequent port registration is the SoundDevice added to the page.
Use Mixxx samplerate and buffer size for PipeWire filter
@pri-yan-shu

Copy link
Copy Markdown
Contributor Author

The one failing test fails on main as well.

@daschuer

daschuer commented Jul 7, 2026

Copy link
Copy Markdown
Member

Or we can initialize/deinitialize directly on Sound API comboBox change. This is the cleanest approach.

Yes this seems the best. We also don't want to touch Portadio if Pipewire is selected.

@daschuer

daschuer commented Jul 8, 2026

Copy link
Copy Markdown
Member

You can always amend the final commit, no fixup! required.

@pri-yan-shu

Copy link
Copy Markdown
Contributor Author

Yes this seems the best. We also don't want to touch Portadio if Pipewire is selected.

I have this working, but its a 250 line diff, so we can introduce this later.

@daschuer daschuer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tested this one and it still work flawlessly.
Thank you very much for this great addition already.

@daschuer
daschuer enabled auto-merge July 8, 2026 07:04
@pri-yan-shu

Copy link
Copy Markdown
Contributor Author

All is fine, although one issue is, when selecting PipeWire API, and connect an input (like Microphone 1) which is not running (so for instance a paused music player), the input emits garbage buffer. It becomes fine once the input (the music player) starts running.

auto-merge was automatically disabled July 8, 2026 07:16

Head branch was pushed to by a user without write access

@pri-yan-shu

Copy link
Copy Markdown
Contributor Author

Right, so I removed zeroing out input buffers, since manually pausing the input devices (like the music player) were handled fine without explicitly zeroing, maybe PipeWire sends a null buffer at pause which is copied to input buffer, but in case of startup while the program is paused, or abrupt disconnect (by patchbay), the same buffer is processed repeatedly. So we need the interleaved zero writes, even though they are needed for one engine iteration, as onwards the buffer is zero (I think). Apologies for the hassle.

@daschuer

daschuer commented Jul 8, 2026

Copy link
Copy Markdown
Member

The CI failure are known unrelated issues. one id fixed here: #16693
For the other one I have something in progress based on that.

@daschuer
daschuer merged commit 337154f into mixxxdj:main Jul 8, 2026
15 of 17 checks passed
Comment thread CMakeLists.txt
target_link_libraries(mixxx-lib PRIVATE PortAudioRingBuffer)

# PipeWire
default_option(PIPEWIRE "Enable the PipeWire backend" "UNIX AND NOT APPLE AND NOT ANDROID")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please disable Pipewire by default!
The proposal is still not approved and thus we shouldn't make this feature available till we agreed we want to keep it such a way.

CC @mixxxdj/developers

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(just stumbled upon this because I don't have libpipewire-3, yet)

I agree. Default off until it's mature / safe/polished enough for test use.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done: #16713

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ronso0 we require actually libpipewire-0.3
Was it just a typo and you have libpipewire-0.3 or is there a distro out there that does not provide it?
I am asking because then we may adjust the CMake code accordingly.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, libpipewire-0.3
Just set the flag to Off until I update my distro.
If I require it before that I'll find a way.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, no problem we can make Pipwire optional, depending on the existence of that file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heads up this makes pipewire dependency optional, it is compiled in by default (helpful for CI) and pipewire features can be accessed by --developer flag. Is that fine?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build cmake developer experience Issues, bugs and PRs related to the development process, development environment & developer docs preferences soundio ui

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants